
package com.hlkj.pay.app.ad.impl;
/*
 * Hlpay-Plus aggregate payment system.
 * Copyright (c) 2024-2025 Hlpay Team Copyright has the right of final interpretation.
 */

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.springframework.stereotype.Service;

import com.hlkj.framework.caches.service.RedisTemplateService;
import com.hlkj.framework.common.util.collection.CollectionUtils;
import com.hlkj.pay.app.ad.IAdPayOrderCallService;
import com.hlkj.pay.app.ad.IAdStatisticsAppService;
import com.hlkj.pay.app.ad.constant.AdConstant;
import com.hlkj.pay.common.constants.AdRedisConstant;
import com.hlkj.pay.dto.ad.AdStatisticsDto;
import com.hlkj.pay.dto.ad.AdStatisticsTimeKeyDto;
import com.hlkj.pay.enums.AdEnum;
import com.hlkj.pay.infrastructure.model.ad.AdInfoDO;
import com.hlkj.pay.infrastructure.model.ad.AdPayOrderDO;
import com.hlkj.pay.infrastructure.model.ad.AdStatisticsDO;
import com.hlkj.pay.service.ad.IAdInfoService;
import com.hlkj.pay.service.ad.IAdStatisticsService;
import com.hlkj.pay.util.AdUtils;
import com.hlkj.pay.util.MerchantUtils;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * @author HlpayTeam
 * @date 2024/10/24 12:41
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class AdStatisticsAppServiceImpl implements IAdStatisticsAppService {

    private final RedisTemplateService redisTemplateService;

    private final AdPayOrderMessageHandler adPayOrderMessageHandler;

    private final IAdStatisticsService adStatisticsService;

    private final IAdInfoService adInfoService;

    private final IAdPayOrderCallService adPayOrderCallService;

    @Override
    public void processStatisticsMessage(List<AdStatisticsDto> adStatisticsList) {
        Map<Long, List<AdStatisticsDto>> adStatisticsMap = adStatisticsList.stream().collect(Collectors.groupingBy(adStatisticsDto -> adStatisticsDto.getAdId()));
        adStatisticsMap.forEach((key, value) -> {
            try {
                log.info("收到统计消息处理 adId:{},size:{}", key, value.size());
                // 汇总处理
                AdInfoDO adInfoDO = processAdInfoStatistics(value);
                for (AdEnum.STATISTICS_DIM_TYPE statisticsDimType : AdEnum.STATISTICS_DIM_TYPE.values()) {
                    // 分段时间处理
                    processTimeStatistics(key, value, statisticsDimType);
                }
            }
            catch (Exception e) {
                e.getMessage();
            }
        });
    }

    @Override
    public void processPaySuccessCall(AdPayOrderDO adPayOrderDO) {
        log.info("异步多线程支付成功消息处理 adId:{},mchOrderNo:{}", adPayOrderDO.getAdId(), adPayOrderDO.getMchOrderNo());
        // 统计完需要计算回传数据
        String payOrderCountKey = AdRedisConstant.AD_PAY_ORDER_COUNT + adPayOrderDO.getAdId();
        String callOrderCountKey = AdRedisConstant.AD_CALL_ORDER_COUNT + adPayOrderDO.getAdId();
        if (redisTemplateService.getExpire(payOrderCountKey) <= 0) {
            log.info("当前设置支付订单数量有效期10分钟 adId:{}", adPayOrderDO.getAdId());
            redisTemplateService.expire(payOrderCountKey, AdConstant.CALL_EXPIRE_TIME);
            if (!redisTemplateService.hasKey(callOrderCountKey)) {
                redisTemplateService.set(callOrderCountKey, 0, AdConstant.CALL_EXPIRE_TIME);
            }
            else {
                redisTemplateService.expire(callOrderCountKey, AdConstant.CALL_EXPIRE_TIME);
            }
        }
        redisTemplateService.incr(payOrderCountKey, 1);
        // 回传计算
        adPayOrderCallService.processCallMessage(adPayOrderDO);
    }

    /**
     * 不同维度的统计
     *
     * @param adStatisticsList
     * @param statisticsDimType
     */
    private void processTimeStatistics(Long adId, List<AdStatisticsDto> adStatisticsList, AdEnum.STATISTICS_DIM_TYPE statisticsDimType) {
        adStatisticsList.stream().forEach(adStatisticsDto -> {
            adStatisticsDto.setTimeKeyDto(AdUtils.getMinuteLevelTimeRange(adStatisticsDto.getTime(), statisticsDimType.getMinute(), null));
        });
        // 计算当前时间段
        // key 当前时间段
        Map<String, List<AdStatisticsDto>> timeRangeMap = adStatisticsList.stream().collect(Collectors.groupingBy(adStatisticsDto -> adStatisticsDto.getTimeKeyDto().getTimeKey()));
        Set<String> timeKeys = timeRangeMap.keySet();
        List<AdStatisticsDO> adStatisticsDbList = adStatisticsService.queryList(adId, timeKeys);
        if (CollectionUtils.isEmpty(adStatisticsDbList)) {
            adStatisticsDbList = new ArrayList<>();
        }
        Map<String, AdStatisticsDO> timeKeyDbMap = adStatisticsDbList.stream()
                .collect(Collectors.toMap(adStatisticsDO -> adStatisticsDO.getTimeKey(), Function.identity(), (t1, t2) -> t1));
        Set<String> timeKeyDbs = timeKeyDbMap.keySet();
        timeRangeMap.forEach((key, value) -> {
            log.info("统计维度 type:{},key:{}", statisticsDimType.getMsg(), key);
            // 存在 进行更新
            if (timeKeyDbs.contains(key)) {
                AdStatisticsDO adStatisticsDO = calcAdStatisticsDim(value);
                AdStatisticsDO adStatistics = timeKeyDbMap.get(key);
                adStatisticsDO.setId(adStatistics.getId());
                adStatisticsService.updateAdStatistics(adStatisticsDO);
            }
            else {
                AdStatisticsTimeKeyDto minuteLevelTimeRange = value.get(0).getTimeKeyDto();
                log.info("新增统计维度 type:{},key:{}", statisticsDimType.getMsg(), minuteLevelTimeRange.getTimeKey());
                AdStatisticsDO adStatisticsDO = calcAdStatisticsDim(value);
                adStatisticsDO.setTimeKey(key);
                adStatisticsDO.setStartTime(minuteLevelTimeRange.getStartTime());
                adStatisticsDO.setEndTime(minuteLevelTimeRange.getEndTime());
                adStatisticsDO.setType(statisticsDimType.getCode());
                adStatisticsDO.setAdId(adId);
                try {
                    adStatisticsService.addAdStatistics(adStatisticsDO);
                }
                catch (Exception e) {
                    log.error("新增统计维度 fail type:{},key:{}, message:{}", statisticsDimType.getMsg(), minuteLevelTimeRange.getTimeKey(), e, e.getMessage(),e);
                    // ad id 与key做了唯一索引 新增失败进行更新
                    AdStatisticsDO statisticsDO = adStatisticsService.queryOne(adId, key);
                    if (statisticsDO == null) {
                        adStatisticsService.addAdStatistics(adStatisticsDO);
                    }
                    else {
                        adStatisticsDO.setId(statisticsDO.getId());
                        adStatisticsService.updateAdStatistics(adStatisticsDO);
                    }
                }
            }
        });
    }

    /**
     * 汇总统计
     *
     * @param adStatisticsList
     */
    AdInfoDO processAdInfoStatistics(List<AdStatisticsDto> adStatisticsList) {
        AdStatisticsDO adStatisticsDO = calcAdStatisticsDim(adStatisticsList);
        AdInfoDO adInfoDO = new AdInfoDO();
        adInfoDO.setSubmitOrderCount(adStatisticsDO.getSubmitOrderCount());
        adInfoDO.setSubmitOrderAmount(adStatisticsDO.getSubmitOrderAmount());
        adInfoDO.setPayOrderCount(adStatisticsDO.getPayOrderCount());
        adInfoDO.setPayOrderAmount(adStatisticsDO.getPayOrderAmount());
        adInfoDO.setRefundCount(adStatisticsDO.getRefundCount());
        adInfoDO.setRefundAmount(adStatisticsDO.getRefundAmount());
        adInfoDO.setViewCount(adStatisticsDO.getViewCount());
        adInfoDO.setCallOrderCount(adStatisticsDO.getCallOrderCount());
        adInfoDO.setId(adStatisticsList.get(0).getAdId());
        adInfoService.updateStatistics(adInfoDO);
        return adInfoDO;
    }

    AdStatisticsDO calcAdStatisticsDim(List<AdStatisticsDto> adStatisticsList) {
        Map<Integer, List<AdStatisticsDto>> statisticsMap = adStatisticsList.stream().collect(Collectors.groupingBy(adStatisticsDto -> adStatisticsDto.getStatisticsType()));
        List<AdStatisticsDto> viewList = statisticsMap.get(AdEnum.STATISTICS_TYPE.VIEW.getCode());
        List<AdStatisticsDto> payOrderList = statisticsMap.get(AdEnum.STATISTICS_TYPE.PAY_ORDER.getCode());
        List<AdStatisticsDto> submitOrderList = statisticsMap.get(AdEnum.STATISTICS_TYPE.SUBMIT_ORDER.getCode());
        List<AdStatisticsDto> refundOrderList = statisticsMap.get(AdEnum.STATISTICS_TYPE.REFUND_ORDER.getCode());
        List<AdStatisticsDto> callList = statisticsMap.get(AdEnum.STATISTICS_TYPE.CALL_ORDER.getCode());
        Integer viewCount = 0;
        Integer callCount = 0;
        Integer payOrderCount = 0;
        Integer submitCount = 0;
        Integer refundCount = 0;
        Double payOrderAmount = new Double(0);
        Double submitAmount = new Double(0);
        Double refundAmount = new Double(0);
        if (!CollectionUtils.isEmpty(viewList)) {
            viewCount = viewList.size();
        }
        if (!CollectionUtils.isEmpty(payOrderList)) {
            payOrderCount = payOrderList.size();
            payOrderAmount = payOrderList.stream().mapToDouble(adStatisticsDto -> new Double(adStatisticsDto.getAmount())).sum();
        }
        if (!CollectionUtils.isEmpty(submitOrderList)) {
            submitCount = submitOrderList.size();
            submitAmount = submitOrderList.stream().mapToDouble(adStatisticsDto -> new Double(adStatisticsDto.getAmount())).sum();
        }
        if (!CollectionUtils.isEmpty(refundOrderList)) {
            refundCount = refundOrderList.size();
            refundAmount = refundOrderList.stream().mapToDouble(adStatisticsDto -> new Double(adStatisticsDto.getAmount())).sum();
        }
        if (!CollectionUtils.isEmpty(callList)) {
            // 回传的数量是放置在金额字段 因为一次回传会有多个订单数量
            Double summed = callList.stream().mapToDouble(adStatisticsDto -> new Double(adStatisticsDto.getAmount())).sum();
            callCount = summed.intValue();
        }
        AdStatisticsDO adStatisticsDO = new AdStatisticsDO();
        adStatisticsDO.setSubmitOrderCount(submitCount);
        adStatisticsDO.setSubmitOrderAmount(MerchantUtils.doubleToBigDecimal(submitAmount));
        adStatisticsDO.setPayOrderCount(payOrderCount);
        adStatisticsDO.setPayOrderAmount(MerchantUtils.doubleToBigDecimal(payOrderAmount));
        adStatisticsDO.setRefundCount(refundCount);
        adStatisticsDO.setRefundAmount(MerchantUtils.doubleToBigDecimal(refundAmount));
        adStatisticsDO.setViewCount(viewCount);
        adStatisticsDO.setCallOrderCount(callCount);
        return adStatisticsDO;
    }

    @Override
    public void addView(Long id) {
        adPayOrderMessageHandler.sendAdStatistics(id, UUID.randomUUID().toString(), null, AdEnum.STATISTICS_TYPE.VIEW, System.currentTimeMillis());
    }

}
